package moe.dao;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import moe.config.Constants;
import moe.entity.Post;
import moe.util.Paging;
import moe.util.PreparedQueryParser;
import moe.util.PreparedQueryParser.PreparedParameters;
import moe.util.QueryBuilder;

public class PostDao extends BaseDao {
	private final static Logger log = Logger.getLogger(PostDao.class.getName());

	private final static String GET_POST = "SELECT * FROM `post` WHERE `id` = ?";

	public PostDao(ConnType connType) {
		super(connType);
	}

	@SuppressWarnings("unchecked")
	public List<Post> listPosts(Paging paging) {
		final List<Post> result = new ArrayList<Post>(paging.getPageSize());
		String sql = "SELECT count(*) FROM `post`";
		if (!evaluateTotalCount(paging, sql)) {
			return result;
		}
		sql = "SELECT * FROM `post` WHERE id <= (SELECT id FROM `post` ORDER BY `id` DESC LIMIT ?, 1) ORDER BY `id` DESC LIMIT ?";
		this.query(sql, new ResultsetHandler() {
			public Object handle(ResultSet rs) throws SQLException {
				while (rs.next()) {
					Post bean = new Post();
					result.add(bean);
					AnnotationColumnMapper.resultSetMap(rs, bean);
				}
				return null;
			}
		}, paging.getPagingItemOffset(), paging.getPageSize());
		return result;
	}

	public Post getPost(long id) {
		Post result = this.query(GET_POST, new ResultsetHandler<Post>() {
			public Post handle(ResultSet rs) throws SQLException {
				Post result = null;
				if (rs.next()) {
					result = new Post();
					AnnotationColumnMapper.resultSetMap(rs, result);
				}
				return result;
			}
		}, id);
		return result;
	}

	public List<Post> getPosts(Long[] ids) {
		QueryBuilder builder = new QueryBuilder("post", null);
		Object[] args = new Object[ids.length];
		Arrays.fill(args, QueryBuilder.PLACEHOLDER);
		builder.in("id", args);
		return this.query(builder.build(), false, new ResultsetHandler<List<Post>>() {
			public List<Post> handle(ResultSet rs) throws SQLException {
				List<Post> result = new LinkedList<Post>();
				while (rs.next()) {
					Post bean = new Post();
					result.add(bean);
					AnnotationColumnMapper.resultSetMap(rs, bean);
				}
				return result;
			}
		}, ids);
	}

	public Map<String, Integer> takeChildrenCount(String[] ids) {
		int length = ids.length;
		Long[] pids = new Long[length];
		Object[] args = new Object[length];
		for (int i = 0; i < length; i++) {
			pids[i] = Long.valueOf(ids[i]);
			args[i] = QueryBuilder.PLACEHOLDER;
		}

		QueryBuilder query = new QueryBuilder("post", null);
		query.projection("parent_id", null).projection("COUNT", "*", "count", false);
		query.in("parent_id", args).group("parent_id");

		return this.query(query.build(), false, new ResultsetHandler<Map<String, Integer>>() {
			public Map<String, Integer> handle(ResultSet rs) throws SQLException {
				Map<String, Integer> result = new HashMap<String, Integer>();
				while (rs.next()) {
					result.put(rs.getString("parent_id"), rs.getInt("count"));
				}
				return result;
			}
		}, pids);
	}

	@SuppressWarnings("unchecked")
	public List<Post> searchPost(Map<String, String[]> query, Paging paging) {
		PreparedQueryParser parser = PreparedQueryParser.getInstance();
		QueryBuilder builder = new QueryBuilder("post", null);
		final PreparedParameters parameters = parser.parse(query, builder);
		String sql = builder.build();
		Object[] params = parameters.getParameters();
		if (!evaluateTotalCount(paging, sql, params)) {
			return Collections.EMPTY_LIST;
		}

		parameters.add(paging.getPagingItemOffset(), Types.INTEGER);
		parameters.add(paging.getPageSize(), Types.INTEGER);
		params = parameters.getParameters();
		builder.limit(QueryBuilder.PLACEHOLDER, QueryBuilder.PLACEHOLDER);
		sql = builder.build();

		if (log.isLoggable(Level.FINE)) {
			log.fine("Execute search query: " + sql);
			StringBuilder paramString = new StringBuilder();
			StringBuilder typeString = new StringBuilder();
			for (Object obj : params) {
				paramString.append(obj).append(", ");
				typeString.append(obj.getClass().getName()).append(", ");
			}
			if (paramString.length() > 0) {
				log.fine("Parameters: " + paramString.substring(0, paramString.length() - 2));
				log.fine("Parameters type: " + typeString.substring(0, typeString.length() - 2));
			}
		}
		return this.query(sql, new ResultsetHandler<List<Post>>() {
			public List<Post> handle(ResultSet rs) throws SQLException {
				List<Post> result = new LinkedList<Post>();
				while (rs.next()) {
					Post bean = new Post();
					result.add(bean);
					AnnotationColumnMapper.resultSetMap(rs, bean);
				}
				return result;
			}
		}, params);
	}

	public long getMaxId() {
		String sql = "SELECT MAX(id) FROM `post`";
		return this.query(sql, new ResultsetHandler<Long>() {
			public Long handle(ResultSet rs) throws SQLException {
				long result = 0;
				if (rs.next()) {
					result = rs.getLong(1);
				}
				return result;
			}
		});
	}

	public void insertPosts(List<Post> list) {
		List<String> sqls = new ArrayList<String>(list.size());
		for (Post post : list) {
			sqls.add(AnnotationColumnMapper.mappingToInsert("post", post));
		}
		executeBatch(sqls);
	}

	public void createDumpTables() {
		createOrAlterDumpTable(false);
	}

	public void alterDumpTables() {
		createOrAlterDumpTable(true);
	}

	private void createOrAlterDumpTable(boolean isAlter) {
		List<String> sqls = loadSqlStatements(Constants.MYSQL_DUMP_TABLE_FILE);
		boolean remove = false;
		for (Iterator<String> iterator = sqls.iterator(); iterator.hasNext();) {
			remove = iterator.next().startsWith("ALTER TABLE");
			if (isAlter) {
				remove = !remove;
			}
			if (remove) {
				iterator.remove();
			}
		}
		try {
			this.executeBatch(sqls);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}
